package com.example.demo.config;

import com.example.demo.annotation.LimitRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Collections;

/**
 * @author xph
 */

@Slf4j
@EnableAspectJAutoProxy
@Component
@Aspect
public class LimitRequestAspect {

    // 扣减线程数的LUA脚本
    private static final DefaultRedisScript<Long> LIMIT_REQUEST_SCRIPT;
    static {
        LIMIT_REQUEST_SCRIPT = new DefaultRedisScript<>();
        LIMIT_REQUEST_SCRIPT.setLocation(new ClassPathResource("limit_request.lua"));
        LIMIT_REQUEST_SCRIPT.setResultType(Long.class);
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Pointcut(value = "@annotation(com.example.demo.annotation.LimitRequest)")
    public void limitRequestPointCut(){

    }

    /**
     * 对注解标注的方法进行限流
     * @param proceed
     */
    @Around(value = "limitRequestPointCut()")
    public Object requestAround(ProceedingJoinPoint proceed) throws Throwable {
        // 1、获取目标方法
        MethodSignature signature = (MethodSignature) proceed.getSignature();
        Method method = signature.getMethod();
        // 2、获取目标方法限流的数量和时长和秒杀的id
        LimitRequest annotation = method.getAnnotation(LimitRequest.class);
        Integer limitCount = annotation.value();
        Long ttl = annotation.ttl();
        long seckillId = annotation.seckillId();
        String key = redisConstant.LIMIT_REQUEST_KEY + seckillId;
        // 3、调用LUA脚本判断是否还有线程可用，没有则抛出异常，给全局异常做处理
        Long execute = redisTemplate.execute(LIMIT_REQUEST_SCRIPT,
                Collections.singletonList(key),
                limitCount.toString(), ttl.toString());
        if (execute.equals(0L)){
            log.error("业务繁忙，请稍后重试，id：{}",seckillId);
            throw new RuntimeException("业务繁忙，请稍后重试");
        }
        // 4、执行目标方法
        Object result = proceed.proceed();
        // 5、将扣减的线程还回去
        redisTemplate.opsForValue().decrement(key);
        return result;
    }

}
